package com.hujing;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 多线程示例3
 */
@Controller
public class ThreadDemo3 {
    
    private Map<String,Integer> values = new HashMap<>();
    ConcurrentHashMap<String,Object> lock = new ConcurrentHashMap<>();
    
    // 考试
    @RequestMapping("/saving")
    public String saving(String school){
        Object o = lock.computeIfAbsent(school, s -> new Object());
        synchronized (o){
            System.out.println(school+"学生交卷");
            save(school);
            System.out.println(school+"学生交卷完毕");
        }
        return "ok";
    }

    // 入学
    @RequestMapping("/saving")
    public String saving2(String school){
        synchronized (school.intern()){
            System.out.println(school+"学生交卷");
            save(school);
            System.out.println(school+"学生交卷完毕");
        }
        return "ok";
    }
    
    private void save(String account){
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        // 同一个学校，数量+1
        if (values.containsKey(account)){
            values.put(account,values.get(account)+1);
        }else{
            values.put(account,1);
        }
    }
    
    private void extracted() throws InterruptedException {
        Thread t1 = new Thread(() -> saving("北大"));
        Thread t2 = new Thread(() -> saving("清华"));
        Thread t3 = new Thread(() -> saving("清华"));
        
        t1.start();
        t2.start();
        t3.start();
        
        t1.join();
        t2.join();
        t3.join();
        
        System.out.println(values);
    }

    public static void main(String[] args) throws InterruptedException {
        // 方法一：直接调用，会出现结果与预期结果不符的情况
        ThreadDemo3 threadDemo3 = new ThreadDemo3();
        threadDemo3.extracted();
        /**
         * 通过以上代码，输出的结果如下，与实际预期结果不符
         * 清华学生交卷
         * 清华学生交卷
         * 北大学生交卷
         * 清华学生交卷完毕
         * 清华学生交卷完毕
         * 北大学生交卷完毕
         * {清华=1, 北大=1}
          */
        /**
         * 方法二：通过synchronized进行加锁，加锁的代码如下：
         *       但是这样加锁有个问题，线程只能一个接着一个执行，但实际情况是北大的学生交卷应该不影响清华的学生交卷
         *       所以，北大与清华应该是需要并行执行的。
         *       性能差
         * synchronized (this){
         *             System.out.println(school+"学生交卷");
         *             save(school);
         *             System.out.println(school+"学生交卷完毕");
         *         }
          */

        /**
         * 方法三：将this关键字改过school，这样不同的学校是可以并行执行，相同的学校是串行执行。
         *       但是这里有个安全隐患，如果是http请求的话，spring的底层传的不是school的常量，而是传的是String的对象，也就是new String("清华")，
         *       这样的话，三个学生又变成并行执行了。
         * synchronized (school){
         *             System.out.println(school+"学生交卷");
         *             save(school);
         *             System.out.println(school+"学生交卷完毕");
         *         }
         */

        /**
         * 方法四：使用school.intern()获取字符串的常量，这样通过http请求就可以达到不同学校并行，相同学校串行的效果了。
         *       但是这里还有个隐患，就是常量池是全局的，如果另外一个接口里也用到school.intern()作为锁的对象，那这2个接口就会互相影响。
         * synchronized (school.intern()){
         *             System.out.println(school+"学生交卷");
         *             save(school);
         *             System.out.println(school+"学生交卷完毕");
         *         }
         */

        /**
         * 方法五：
         * ConcurrentHashMap<String,Object> lock = new ConcurrentHashMap<>();
         * Object o = lock.computeIfAbsent(school, s -> new Object());
         *         synchronized (o){
         *             System.out.println(school+"学生交卷");
         *             save(school);
         *             System.out.println(school+"学生交卷完毕");
         *         }
         * 这种方式是通过computeIfAbsent方法，如果不存在，则创建新的Object对象，然后加锁，这样就保证了不同学校的并行执行。
         * 
         */
    }
}
